-- ============================================================
-- metatable for table.
-- ============================================================
-- 

local p = print
local str = tostring
-- an empty table used as a key in Lua is an unique key.
local unique_key = {}

function clear()
    local i=0
    repeat
        print()
        i = i + 1
    until i > 40
end

-- window inheritance
local window = {}
window.prototype = {
    x = 0,
    y = 0,
    width = 100,
    height = 100
}

window.metatable = {
    __index = function (t, k)
        --[[
        if t.k then
            return t.k
        else
            return window.prototype[k]
        end
        --]]
        return window.prototype[k]
    end
}

-- or 
--[[
window.metatable.__index = window.prototype
--]]

function window.new(t)
    setmetatable(t, window.metatable)
    return t
end

local w1 = window.new{x=10, y=20}
-- w1.x w1.y will visit table w1 directly
print(w1.x)
print(w1.y)
-- w1.width w1.height will visit table w1 first, 
-- then visit through __index method after failing to find width and height fields in w1.
print(w1.width)
print(w1.height)


-- ============================================================
-- table with default value
-- ============================================================

-- implementation
local default_value_metatable = {}

default_value_metatable.__index = function (t)
    return t[unique_key]
end

function set_default_value(t, value)
    t[unique_key] = value
    setmetatable(t, default_value_metatable)
end


clear()
-- use case
local dft1 = {}
print(dft1.x)

set_default_value(dft1, 10)
print(dft1.x)

local dft2 = {}
set_default_value(dft2, 19)
p(dft2.x)
p(dft1.x)

-- ============================================================
-- trace a table's visit
-- ============================================================
local origin = {
    x = 10,
    y = 20,
    z = 30
}

local _ref_origin = origin

-- origin must be an empty table, so that we can redirect all the visit to __index and __newindex.
origin = {}

local metatable_for_tracing_origin = {
    __index = function (t, k)
        p("visit table element: " .. str(k))
        return _ref_origin[k]
    end,
    __newindex = function (t, k, v)
        p("update field " .. str(k) .. " to " .. str(v))
        _ref_origin[k] = v
    end
}
setmetatable(origin, metatable_for_tracing_origin)

clear()
p(origin.x)

origin.z = 1000
p(origin.z)

-- disadvantage of this solution: can't use 'pairs' with origin, because origin is empty
--
-- metatable_for_tracing_origin is wasteful because this metatable can only use with origin,
-- cannot be reused.

-- ============================================================
-- tracing table with only one metatable -- my version
-- ============================================================
local origin1 = {
    x = 11,
    y = 12,
}

local origin2 = {
    x = 21,
    y = 22,
}

local _ref_origin1 = origin1
origin1 = {
    unique_key = _ref_origin1
}

local _ref_origin2 = origin2
origin2 = {
    unique_key = _ref_origin2
}

local reused_metatable_for_tracing_table = {
    __index = function (t, k)
        p("visit " .. str(t) .. "with key : " .. str(k))
        return t.unique_key[k]
    end,

    __newindex = function (t, k, v)
        p("update " .. str(t) .. " [" .. str(k) .. "] = " .. str(v))
        t.unique_key[k] = v
    end
}

setmetatable(origin1, reused_metatable_for_tracing_table)
setmetatable(origin2, reused_metatable_for_tracing_table)

clear()
p(origin1.x)
origin1.x = 111
p(origin1.x)

p(origin2.y)
origin2.y = 222
p(origin2.y)

-- ============================================================
-- tracing table with only one metatable -- version from Programming In Lua book
-- ============================================================
local metatable_tracing_table = {
    __index = function(t, k)
        p("visit " .. str(t[unique_key]) .. "with key : " .. str(k))
        return t[unique_key][k]
    end,

    __newindex = function (t, k, v)
        p("update " .. str(t[unique_key]) .. " [" .. str(k) .. "] = " .. str(v))
        t[unique_key][k] = v
    end
}

function trace(t)
    local proxy = {}
    proxy[unique_key] = t
    setmetatable(proxy, metatable_tracing_table)
    return proxy
end

local traced1 = {
    x = -1,
    y = -2
}

clear()
p(traced1.x)
p(traced1.y)

traced1 = trace(traced1)    -- trace table:traced1
p(traced1.x)
p(traced1.y)
traced1.x = -11
p(traced1.x)
traced1.y = -22
p(traced1.y)


-- ============================================================
-- read-only-table
-- ============================================================
local read_only_metatable = {
    __index = function (t, k)
        return t[unique_key][k]
    end,

    __newindex = function (t, k, v)
        p(str(t[unique_key]) .. "[" .. str(k) .. "] = " .. str(t[unique_key][k]))
        p(str(t[unique_key]) .. " is read only table.")
    end
}

function read_only(t) 
    local proxy = {}
    proxy[unique_key] = t
    setmetatable(proxy, read_only_metatable)
    return proxy
end

--use case
local readonly1 = {
    x = 101,
    y = 102,
}

clear()
p(readonly1.x)
readonly1.x = 2011
p(readonly1.x)

readonly1 = read_only(readonly1)
readonly1.x = 2012
p(readonly1.x)

readonly1.y = 2022
p(readonly1.y)

